(in-package "ACL2")
(include-book "../../include/src/io-utilities")
(include-book "command-handler")
(include-book "world")
(include-book "grid")
(include-book "state-writer")

(set-state-ok t)
;;;Module for parsing commands written by python
;;;Handles finding the new commands in the commands.txt file,
;;;splitting them appropriately, and calling functionsi in the command
;;;handler to update thme


;;all commands
(defconst *play* (str->chrs "play"))
(defconst *pause* (str->chrs "pause"))
(defconst *fast-forward* (str->chrs "fast-forward"))
(defconst *save-img* (str->chrs "save-img"))
(defconst *load-img* (str->chrs "load-img"))
(defconst *set-cell* (str->chrs "set-cell"))

;;separators to split on
(defconst *param-sep* #\Space)
(defconst *command-seps* (list #\Newline))

;;canonical character list representations of
;;true and false
(defconst *true* (str->chrs "true"))
(defconst *false* (str->chrs "false"))

;;Convert a list of character
;;into a rational number matching its
;;string represnetation
(defun chrs->rat (chrs)
  (str->rat (chrs->str chrs)))

;;(str->chrs "true") -> t
;;(str->chrs "false") -> nil
;;anything else -> error
(defun chrs->bool (chrs)
  (if (equal chrs *false*)
    nil
    (if (equal chrs *true*)
      t
      (concatenate 'string 
        "error in chrs-bool: " (str->chrs chrs)))))

;;Join a list of lists of characters, with space separators
(defun join-chrs* (chrss)
   (if (endp chrss)
       nil
       (append (car chrss) (list #\Space) (join-chrs* (cdr chrss)))))
   

;;Perform the actual execution of a command
(defun execute-command-helper* (command wld state)
   (let* ((command-packets (packets *param-sep* command))
          (name (car command-packets))
          (params (cdr command-packets)))
  (cond ((equal name *play*)
            (mv (play wld) state))
        ((equal name *pause*)
            (mv (pause wld) state))
        ((equal name *fast-forward*)
            (mv (fast-forward wld) state))
        ((equal name *save-img*)
            (handle-save-img wld (join-chrs* params) state))
        ((equal name *load-img*)
          (mv-let (new-wld state)
            (handle-load-img wld (join-chrs* params) state)

            ;write out to the grid after loading.
            (let* ((ruletype (ruleset-rule-type (cell-world-rules new-wld)))
                   (state (write-changes (flatten-grid (cell-world-grid new-wld) 
                                                        ruletype)
                                           ruletype
                                           (cell-world-gen-num new-wld)
                                           state)))
              (mv (increment-world-gen-num new-wld) state))))
        ((equal name *set-cell*)
         (if (equal (ruleset-rule-type (cell-world-rules wld))
                    "lifelike")
            (let* ((x (chrs->rat (car params)))
                   (y (chrs->rat (cadr params)))
                       (val (chrs->bool (caddr params))))
            	(mv (set-cell wld x y val) state))
         	  (let* ((x (chrs->rat (car params)))
                  (val (chrs->bool (cadr params))))
            	(mv (set-cell wld x 0 val) state))))
        ((equal name nil)
            ;hack: ignore it if python writes an empty line
            (mv wld state))
        (t
          ;Python wrote a command with an unexpected command.
          ;Since signaling an error would cause problems with
          ;DrRacket, let's replace the world with a helpful
          ;string and hope it shows up in the debugger when the
          ;string makes it crash.
          (mv (string-append "error: unexpected command " (chrs->str name))
               state)))))

;;Call execute-command** to perform the actual execution,
;;then increase the command execution count
(defun execute-command* (command wld state)
   (mv-let (new-wld state)
           (execute-command-helper* command wld state)
         (mv (increment-num-commands-read new-wld) state)))

;;Loop through list of commands to execute, calling
;;execute-commands* on each one.
;;returns (mv wld state)	 
(defun execute-commands* (commands wld state)
    (if (endp commands)
        (mv wld state)
        (let* ((top-command (car commands))
               (rest-commands (cdr commands)))
          (mv-let (new-wld state)
            (execute-command* top-command wld state)
            (execute-commands* rest-commands new-wld state)))))

;;Tokenize a list of characters into a list of commands.
;;The returned value is a list of lists of characters,
;;where each inner list is a command followed by any params.
(defun command-tokenizer* (command-chrs)
  ;Use packets-set, but treat empty command-chrs as NIL,
  ;not '(nil)
  (if (endp command-chrs)
    nil
    (packets-set *command-seps* command-chrs)))

;;get all commands, drop the ones we've already read,
;;pass the rest to execute-commands*
(defun parse-commands* (command-chrs wld state)
  (let* ((commands (command-tokenizer* command-chrs))
         (new-commands (nthcdr (cell-world-num-commands-read wld)
                               commands)))
    (execute-commands* new-commands wld state)))
	
;;Adapted from functions in list-utilities.
(defun read-command-file (file wld state)
    (mv-let (channel state) 
       (open-input-channel file :character state)
          (if (null channel)
              (mv wld state)
              (mv-let (chlist chnl state)
                      (read-n-chars 4000000000 nil channel state)
                 (let ((state (close-input-channel chnl state)))
                   (parse-commands* (reverse chlist) wld state))))))